/* This file is part of GSMC.
 * 
 * GSMC is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GSMC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with GSMC.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.hpxn.gsmc.core.server;

import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;

import net.hpxn.gsmc.action.BaseAction;
import net.hpxn.gsmc.action.util.ActionClassLoader;
import net.hpxn.gsmc.config.server.ServerConfigs.ServerConfig;
import net.hpxn.gsmc.config.server.ServerConfigs.ServerConfig.StartupActions;
import net.hpxn.gsmc.config.server.ServerConfigs.ServerConfig.StartupActions.Action;
import net.hpxn.gsmc.core.ThreadPool;
import net.hpxn.gsmc.core.event.TF2Event;
import net.hpxn.gsmc.core.server.exceptions.ServerCommunicationException;
import net.hpxn.gsmc.core.server.exceptions.ServerUnavailableException;

import org.apache.log4j.Logger;
import org.eclipse.jetty.servlet.ServletContextHandler;

/**
 * Represents a game server.
 * 
 * @author peter
 * 
 */
public abstract class Server {

	private static final long serialVersionUID = -964508369225099123L;

	private ServerConfig serverConfig;

	private String hostname;
	private String version;
	private String mapName;

	private int playerCount;
	private int maxPlayers;

	private HashMap<Integer, String> playerList;
	private static ArrayList<Server> serverList;

	private ActionClassLoader classLoader;
	
	private ServletContextHandler servletContext;
	
	private boolean isUp;

	protected Logger log;

	public Server( ServerConfig pServerConfig ) {
		this.serverConfig = pServerConfig;
		classLoader = new ActionClassLoader( "actions" + File.separatorChar );
		log = Logger.getLogger( getClass().getCanonicalName() );
		isUp = true;
		
		hostname = getRemoteIp();
		version = "N/A";
		mapName = "N/A";
		playerCount = 0;
		maxPlayers = 0;
	}

	@SuppressWarnings( "rawtypes" )
	public void startupActions() {
		for ( StartupActions wStartupActions : serverConfig.getStartupActions() ) {
			for ( Action wAction : wStartupActions.getAction() ) {
				try {
					Class c = classLoader.loadClass( wAction.getValue() );
					BaseAction wBaseAction = (BaseAction) c.newInstance();
					
					wBaseAction.setEvent( new TF2Event( "", this ) );
					
					ThreadPool.getInstance().execute( wBaseAction );
				} catch ( Exception e ) {
					log.error( "Unable to load startup action: "
							+ wAction.getValue() + "\n" + e.getMessage() );
				}
			}
		}
	}

	/**
	 * Updates the server status. Usually called by the StatusUpdateAction but 
	 * other actions can call this to refresh the server information 
	 * immediately.
	 */
	public abstract void updateServerStatus() throws ServerUnavailableException;

	/**
	 * Sets the hostname of the server. This is usually called by the 
	 * StatusUpdateAction. This does not actually change the hostname 
	 * on the server it only serves as an indicator of what it actually is.
	 * 
	 * @param String
	 *            - The host name of the server.
	 */
	public synchronized void setHostname( String hostname ) {
		this.hostname = hostname;
	}

	/**
	 * Sets the server version. This is usually called by the 
	 * StatusUpdateAction. This does not actually change the version 
	 * on the server it only serves as an indicator of what it actually is.
	 * 
	 * @param String
	 *            - The version of the server.
	 */
	public synchronized void setVersion( String version ) {
		this.version = version;
	}

	/**
	 * Sets the current map name. This is usually called by the 
	 * StatusUpdateAction. This does not actually change the map 
	 * on the server it only serves as an indicator.
	 * 
	 * @param String
	 *            - The name of the map.
	 */
	public synchronized void setMapName( String mapName ) {
		this.mapName = mapName;
	}

	/**
	 * Sets the current player count. This is usually called by the 
	 * StatusUpdateAction. This does not actually change the player count 
	 * on the server it only serves as an indicator.
	 * 
	 * @param int - The number of players.
	 */
	public synchronized void setPlayerCount( int playerCount ) {
		this.playerCount = playerCount;
	}

	/**
	 * Sets the maximum amount of players that are allowed on the server. This
	 * is usually called by the StatusUpdateAction. This does not actually 
	 * change the max player count on the server it only serves as an indicator.
	 * 
	 * @param int - The max number of players.
	 */
	public synchronized void setMaxPlayers( int maxPlayers ) {
		this.maxPlayers = maxPlayers;
	}

	/**
	 * Sends a command to the game server and returns the raw response.
	 * 
	 * @param String
	 *            - The message to send.
	 * @return String
	 */
	public abstract String sendRaw( String pMsg ) throws ServerCommunicationException;

	/**
	 * Returns the server status from the server. Override this for a specific 
	 * check.
	 * 
	 * @return String
	 */
	protected String getServerStatus() throws ServerCommunicationException {
		Socket socket = new Socket();
		InetSocketAddress endPoint =
			new InetSocketAddress( getRemoteIp(), getRemotePort() );

		if ( endPoint.isUnresolved() ) {
			throw new ServerCommunicationException( "Unable to resolve: "
							+ getRemoteIp() );
		}
		
		try {
			socket.connect( endPoint, 5000 );
		} catch ( IOException e ) {
			throw new ServerCommunicationException( e );
		} finally {
			try {
				socket.close();
			} catch ( IOException ioe ) {
				// Whatever.
			}
		}
		return null;
	}

	/**
	 * Returns an ArrayList of servers that were configured in the server.xml
	 * file. This method is static.
	 * 
	 * @return ArrayList
	 */
	public static ArrayList<Server> getServerList() {
		return serverList;
	}

	/**
	 * Returns the name of the server. This is configured in the server.xml
	 * file.
	 * 
	 * @return String
	 */
	public String getName() {
		return serverConfig.getName();
	}

	/**
	 * Returns the ip address of the remote game server. This is configured in
	 * the server.xml file.
	 * 
	 * @return String
	 */
	public String getRemoteIp() {
		return serverConfig.getRemoteIp();
	}

	/**
	 * Returns the port of the remote game server. This is configured in the
	 * server.xml file.
	 * 
	 * @return int
	 */
	public int getRemotePort() {
		return Integer.parseInt( serverConfig.getRemotePort() );
	}

	/**
	 * Returns if the server events are being logged or not. (Currently not
	 * used)
	 * 
	 * @return boolean
	 */
	public boolean isLog() {
		return Boolean.parseBoolean( serverConfig.getLog() );
	}

	/**
	 * Returns the game type of the server. This is configured in the server.xml
	 * file. Eg: tf2
	 * 
	 * @return String
	 */
	public String getGameType() {
		return serverConfig.getType();
	}

	/**
	 * Returns the rcon password for the current server. This is configured in
	 * the server.xml file.
	 * 
	 * @return
	 */
	public String getRconPassword() {
		return serverConfig.getRconPassword();
	}

	/**
	 * Returns the hostname of the remote game server.
	 * 
	 * @return String
	 */
	public synchronized String getHostname() {
		return this.hostname;
	}

	/**
	 * Returns the version of the game server.
	 * 
	 * @return String
	 */
	public synchronized String getVersion() {
		return this.version;
	}

	/**
	 * Returns the current map being played on the server.
	 * 
	 * @return String
	 */
	public synchronized String getMapName() {
		return this.mapName;
	}

	/**
	 * Returns the amount of players currently playing on the server.
	 * 
	 * @return int
	 */
	public synchronized int getPlayerCount() {
		return this.playerCount;
	}

	/**
	 * Returns the max amount of players allowed on the server.
	 * 
	 * @return int
	 */
	public synchronized int getMaxPlayers() {
		return maxPlayers;
	}

	/**
	 * Returns the group that this server belongs to. This is configured in the
	 * server.xml file.
	 * 
	 * @return int
	 */
	public int getGroup() {
		return Integer.parseInt( serverConfig.getGroup() );
	}
	
	/**
	 * Returns the server configuration object.
	 * 
	 * @return ServerConfig
	 */
	public ServerConfig getServerConfig() {
		return this.serverConfig;
	}

	/**
	 * Currently unused.
	 * 
	 * @param pPlayerId
	 * @param pPlayerName
	 */
	public synchronized void addPlayer( Integer pPlayerId, String pPlayerName ) {
		playerList.put( pPlayerId, pPlayerName );
	}

	/**
	 * Currently unused.
	 * 
	 * @param pPlayerId
	 */
	public synchronized void delPlayer( Integer pPlayerId ) {
		if ( playerList.get( pPlayerId ) != null ) {
			playerList.remove( pPlayerId );
		}
	}

	/**
	 * Currently unused.
	 * 
	 * @return
	 */
	public synchronized HashMap<Integer, String> getPlayerList() {
		return playerList;
	}

	/**
	 * When the web server is enabled this will return the serlvet context to 
	 * allow actions to add their own servlets into the context.
	 * 
	 * @return ServletContextHandler
	 */
	public ServletContextHandler getServletContext() {
		return servletContext;
	}
	
	/**
	 * Used to set the ServletContext if the web server is enabled in the 
	 * configuration file.
	 * 
	 * @param servletContext
	 */
	public void setServletContext( ServletContextHandler servletContext ) {
		this.servletContext = servletContext;
	}
	
	/**
	 * Returns if the server is up or not.
	 * 
	 * @return boolean
	 */
	public boolean isUp() {
		return isUp;
	}
	
	/**
	 * Sets the server state. For the most part this will only be used by the 
	 * StatusUpdateAction. It has no affect on the server itself it only serves 
	 * as an indicator.
	 * 
	 * @param isUp
	 */
	public void setUp( boolean isUp ) {
		this.isUp = isUp;
	}

	@Override
	public String toString() {
		return " " + getMapName() + "(" + getPlayerCount() + "/"
				+ getMaxPlayers() + ")";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((serverConfig == null) ? 0 : serverConfig.hashCode());
		return result;
	}

	@Override
	public boolean equals( Object obj ) {
		if ( this == obj )
			return true;
		if ( obj == null )
			return false;
		if ( getClass() != obj.getClass() )
			return false;
		Server other = (Server) obj;
		if ( serverConfig == null ) {
			if ( other.serverConfig != null )
				return false;
		} else if ( !serverConfig.equals( other.serverConfig ) )
			return false;
		return true;
	}
}
